package jp.aist.gtrc.plus.scheduler.algorithm;

import java.security.Security;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import jp.aist.gtrc.plus.admission.EDF.Job;
import jp.aist.gtrc.plus.admission.EDF.Schedule;
import jp.aist.gtrc.plus.admission.AdmissionOperationManager;
import jp.aist.gtrc.plus.admission.AdmissionServer;
import jp.aist.gtrc.plus.scheduler.job.JobSortKey;
import jp.aist.gtrc.plus.scheduler.job.JobStateType;
import jp.aist.gtrc.plus.scheduler.node.NodeInfo;
import jp.aist.gtrc.plus.scheduler.specific.pbs.status.PBSJobStatus;
import jp.aist.gtrc.plus.scheduler.status.JobStatus;
import jp.aist.gtrc.plus.scheduler.status.NodeStatus;
import jp.aist.gtrc.plus.scheduler.status.StatusManager;
import jp.aist.gtrc.plus.scheduler.util.Filter;
import jp.aist.gtrc.plus.scheduler.util.Sorter;

import org.apache.log4j.Logger;

/*
 * Job scheduling: SmartScheduler
 *   Assign QoS jobs first before normal jobs.
 *   The assignment of QoS jobs follows the admission control schedule.
 *
 */
public class SmartScheduler implements Scheduler {
        public static final int MAX_DELAY_TIME = 60; // seconds

	protected static final Logger logger = Logger.getLogger(SmartScheduler.class);


        private AdmissionOperationManager admissionOperationMgr;
        private StatusManager statusMgr;

        // for best effort job scheduling
        private Sorter<JobStatus> fifoJobSorter;
        private Filter<JobStatus> bestEffortJobFilter; 

        public SmartScheduler(StatusManager statusMgr, AdmissionServer admissionServer) {
           assert(admissionServer != null);
           logger.debug("SmartScheduler constructor()");
           this.statusMgr = statusMgr;
           this.admissionOperationMgr = (AdmissionOperationManager) admissionServer;

           this.fifoJobSorter = new Sorter<JobStatus>();
           this.fifoJobSorter.addSortKey(JobSortKey.SubmitTime);
           this.bestEffortJobFilter = new Filter<JobStatus>();
           this.bestEffortJobFilter.addChecker(new BestEffortJobChecker());
        }

        public void preSchedule(Collection<JobStatus> jobs, Collection<NodeInfo> nodes) {

        }

        /* 
         * 1. Find an idle processor core
         * 2. Dispatch a job to the core
         */
        public Collection<Allocation> doScheduling(Collection<JobStatus> jobs, Collection<NodeInfo> nodes) {

           Collection<Allocation> allocs = new ArrayList<Allocation>();
           if (jobs == null) return allocs;
System.err.println("doScheduling() with " + jobs.size() + " best effort or QoS jobs in queue");

           Schedule schedule = admissionOperationMgr.getSchedule();
           if (nodes == null) return allocs;
           if (schedule != null) System.out.println(schedule.toString());

           int now = (int) (Calendar.getInstance().getTimeInMillis()/1000);

           for (NodeInfo nodeInfo : nodes) {
              NodeStatus nodeStatus = nodeInfo.getStatus();
              if (nodeStatus.isIdle()) {
                 System.out.println("Node " + nodeStatus.getName() + " is idle now.");
                 Job job = null;
                 if (schedule != null) job = schedule.getNextJob(nodeStatus.getName());
                 if (job != null) {  // a QoS job is waiting for this node
                    System.out.println("SmartScheduler selects QoS job " + job.name);
                    JobStatus jobStatus = statusMgr.getJob(job.name);
                    if (jobStatus == null) {
//
// Bug. jobStatus is null but the following instruction access jobStatus.
/*
                       int latestDelayTime = ((int) (jobStatus.getSubmitTime().getTimeInMillis()/1000)) + SmartScheduler.MAX_DELAY_TIME; // found lost job in PBS
                       if (latestDelayTime > now) {
                          schedule.removeJob(nodeStatus.getName(),job); // the selected job is no longer considered in the next round
                       } else {
                          System.err.println("QoS job " + job.name + " is found in schedule but missing in PBS due to delay in PBS");
                       }
*/
                       System.err.println("jobStatus of " + job.name + " is missing from pbs. Continue to another node.");
                       continue; // let's turn to another node
                    }
                    if (jobStatus.getState() == JobStateType.Held) {
                       int latestConfirmationTime = ((int) (jobStatus.getSubmitTime().getTimeInMillis()/1000)) + AdmissionOperationManager.MAX_CONFIRMATION_TIME;
                       if (latestConfirmationTime > now) {
                          System.err.println("QoS Job " + job.name + " is still waiting for confirmation"); 
                       } else {
                          System.err.println("QoS job " + job.name + " is held. Discard the job. Check job validator");
                       }
                       continue;
                    }
                    System.out.println("Dispatch QoS job " + job.name + " on " + nodeInfo.getName());
                    Collection<NodeInfo> allocatedNodes = new ArrayList<NodeInfo>();
                    allocatedNodes.add(nodeInfo); // just one allocated node
                    allocs.add(new Allocation(jobStatus,allocatedNodes)); 
                    schedule.removeJob(nodeStatus.getName(),job); // the selected job is no longer considered in the next round
                    break; // one job and one node at a time

                 } else { // no more QoS jobs in this node queue, select a best effort job
                    System.out.println("No more QoS job for node " + nodeStatus.getName() + ". Try with best effort job");
                    Collection<JobStatus> bestEffortJobs = bestEffortJobFilter.filter(jobs);
                    if ( (bestEffortJobs == null)  || (bestEffortJobs.size() == 0) ) {
                       System.out.println("No more best effort job");
                       continue;  // no more best effort jobs, check next node
                    }
                    JobStatus[] jobArr = bestEffortJobs.toArray(new JobStatus[bestEffortJobs.size()]);
                    fifoJobSorter.sort(jobArr);
                    JobStatus jobStatus = jobArr[0];
                    System.out.println("Dispatch best effort job " + jobStatus.getName() + " on " + nodeInfo.getName());
                    Collection<NodeInfo> allocatedNodes = new ArrayList<NodeInfo>();
                    allocatedNodes.add(nodeInfo); // just one allocated node
                    allocs.add(new Allocation(jobStatus,allocatedNodes)); 
                    break; // one job and one node at a time
                 }
              }
           }

           return allocs; 
        }

        public void postSchedule(Collection<JobStatus> jobs, Collection<NodeInfo> nodes, Collection<Allocation> allocs) {

        }

}
